use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use shq_common::prelude::{ActionKind, ContextAction, ContextMenuItem, ContextMenus, Drawer, Trigger, TriggerMode};

use super::{get_rept_config, Report};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DrillDown {
    pub title: String,
    #[serde(default)]
    pub title_columns: Vec<String>,
    pub trigger: Trigger,
    pub reports: DrillDownReports,
    #[serde(default)]
    pub is_default: bool,
    #[serde(default)]
    pub drawer: Drawer,
}
impl DrillDown {
    pub fn to_context_menu_item(&self) -> ContextMenuItem {
        let acts = match self.reports {
            DrillDownReports::Single(ref item) => {
                vec![item.to_context_action()]
            }
            DrillDownReports::Multiple(ref items) => items
                .iter()
                .map(|item| item.to_context_action())
                .collect(),
        };
        let item = ContextMenuItem {
            caption: self.title.clone(),
            title_columns: self.title_columns.clone(),
            drawer: self.drawer.clone(),
            context_actions: acts,
            is_default: self.is_default,
            trigger: self.trigger.clone(),
        };
        item
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DrillDownReports {
    Single(DrillDownItem),
    Multiple(Vec<DrillDownItem>),
}
impl DrillDownReports {
    pub(super) fn check_param(
        &self,
        col_names: &[&str],
        params: &[&str],
        conf_map: Option<&HashMap<String, &Report>>,
    ) -> anyhow::Result<()> {
        match self {
            DrillDownReports::Single(ddi) => {
                ddi.check_param(col_names, params, conf_map)?;
            }
            DrillDownReports::Multiple(ddis) => {
                for ddi in ddis {
                    ddi.check_param(col_names, params, conf_map)?;
                }
            }
        }
        Ok(())
    }
    pub(super) fn get_rept_names(&self) -> Vec<&str> {
        match self {
            DrillDownReports::Single(ddi) => vec![ddi.rept_name.as_str()],
            DrillDownReports::Multiple(ddis) => {
                ddis.iter().map(|ddi| ddi.rept_name.as_str()).collect()
            }
        }
    }
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DrillDownItem {
    pub rept_name: String,
    #[serde(default = "default_grid")]
    pub grid: u16,
    pub param_map: HashMap<String, String>,
}
fn default_grid() -> u16 {
    12
}
impl DrillDownItem {
    pub(super) fn check_param(
        &self,
        col_names: &[&str],
        params: &[&str],
        conf_map: Option<&HashMap<String, &Report>>,
    ) -> anyhow::Result<()> {
        let rept2 = if let Some(conf_map) = conf_map {
            conf_map.get(self.rept_name.as_str()).copied()
        } else {
            get_rept_config(self.rept_name.as_str())
        };
        if rept2.is_none() {
            return Err(anyhow::anyhow!(
                "drill_down-rept_name:{}不存在",
                self.rept_name
            ));
        }
        let rept2 = rept2.unwrap();
        for p in rept2.params.iter() {
            if !self.param_map.contains_key(p.name.as_str()) {
                anyhow::bail!(
                    "drill_down配置项下的报表rept_name:{}中的param_map参数{}没有配置",
                    self.rept_name,
                    p.name
                )
            }
        }
        for (k, v) in self.param_map.iter() {
            if v.starts_with("@") {
                let pv = &v[1..];
                if !params.contains(&pv) {
                    anyhow::bail!(
                        "drill_down配置项下的报表rept_name:{}中的param_map的value:{}不在参数中",
                        self.rept_name,
                        v
                    );
                }
            } else if v.starts_with("{") && v.ends_with("}") {
                let cv = &v[1..v.len() - 1];
                if !col_names.contains(&cv) {
                    anyhow::bail!("drill_down配置项下的报表rept_name:{}中的param_map的value:{}不在报表columns定义中", self.rept_name,cv);
                }
            } else if v.starts_with("#") {
                if v != "#COLEXP" {
                    anyhow::bail!(
                        "drill_down配置项下的报表rept_name:{}中的param_map的value:{}无效",
                        self.rept_name,
                        v
                    );
                }
            }
            if !rept2.params.iter().any(|p| p.name == *k) {
                anyhow::bail!(
                    "param_map的key:{}不在报表{}的parameters定义中",
                    k,
                    self.rept_name
                )
            }
        }
        Ok(())
    }
    pub fn to_context_action(
        &self,
    ) -> ContextAction {
        let conf = get_rept_config(self.rept_name.as_str()).unwrap();
        ContextAction {
            kind: ActionKind::Report,
            name: self.rept_name.clone(),
            param_map: self.param_map.clone(),
            auth_filter: conf.auth_filter.as_ref().map(|af| af.clone()),
        }
    }
}
impl DrillDown {
    pub(super) async fn parse(
        &self,
        col_names: &[&str],
        params: &[&str],
        conf_map: Option<&HashMap<String, &Report>>,
    ) -> anyhow::Result<()> {
        for col_name in &self.title_columns {
            if !col_names.contains(&col_name.as_str()) {
                anyhow::bail!(
                    "drill_downs-title_columns:{}不在报表columns定义中",
                    col_name
                );
            }
        }
        match &self.trigger.mode {
            TriggerMode::CellInCols | TriggerMode::Col => {
                if self.trigger.col_name_where_map.is_empty() {
                    anyhow::bail!(
                        "drill_downs-trigger_mode为CellInCols时，col_name_where_map不能为空"
                    );
                }
                for col in self.trigger.col_name_where_map.keys() {
                    if !col_names.contains(&col.as_str()) {
                        anyhow::bail!("drill_downs-trigger_mode为CellInCols，col_name_where_map:{}不在报表columns定义中", col);
                    }
                }
            }
            _ => {}
        }
        self.reports.check_param(col_names, params, conf_map)?;
        Ok(())
    }
}

pub fn to_context_menus(
    drill_downs: &[DrillDown]
) -> Option<ContextMenus> {
    if drill_downs.is_empty() {
        return None;
    }
    let mut cms = ContextMenus::new();
    for dd in drill_downs.iter() {
        cms.push(dd.to_context_menu_item());
    }
    Some(cms)
}



